package com.tsyz.netty.client;

import com.tsyz.netty.server.ServerHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

public class NettyClient {
    /**
     * 定义客户端在连接断开后尝试重新连接的最大次数。
     */

    private static final int MAX_RETRY_TIMES = 5;
    /**
     * 定义客户端在连接断开后重试连接的间隔时间为5秒。
     */
    private static final long RETRY_INTERVAL = 5000; // 5秒
    /**
     * 存储Netty客户端要连接的服务器的地址（主机名或IP地址）。
     */
    private final String host;
    /**
     * 存储Netty客户端要连接的服务器的端口号。
     */
    private final int port;
    /**
     * 用于缓存客户端在未连接状态下未能发送的消息队列
     */

    private final Queue<String> messageCache = new ConcurrentLinkedQueue<>();

    /**
     * 表示与服务器建立的网络连接通道，用于数据的发送和接收。
     */
    private Channel channel;

    /**
     * 用于管理Netty客户端的I/O操作和线程模型的事件循环组。
     */
    private EventLoopGroup group;
    /**
     * 标识当前客户端是否已成功连接到服务器的状态标志
     */
    private volatile boolean isConnected = false;

    /**
     * 创建一个新的Netty客户端实例，连接到指定的主机和端口。
     *
     * @param host 要连接的主机名或IP地址。
     * @param port 要连接的端口号。
     */
    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    /**
     * 启动Netty客户端连接到指定的IP和端口。
     *
     * @param args 命令行参数，当前未使用。
     */
    public static void main(String[] args) throws InterruptedException {
        new NettyClient("127.0.0.1", 8080).connect();
    }

    /**
     * 建立到指定主机和端口的连接，并处理用户输入。
     *
     * @throws InterruptedException 如果在等待连接关闭时被中断
     */
    public void connect() throws InterruptedException {
        group = new NioEventLoopGroup();
        try {
            doConnect();

            System.out.println("连接到服务器 " + host + ":" + port);

            // 启动用户输入
            handleUserInput();

            // 等待连接关闭
            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    /**
     * 建立与服务器的连接。
     *
     * @throws InterruptedException 如果当前线程被中断，会抛出这个异常。
     */
    private void doConnect() throws InterruptedException {
        if (group.isShuttingDown() || group.isShutdown()) {
            // 如果事件循环组已关闭，重新创建一个
            group = new NioEventLoopGroup();
        }

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            /**
             * 初始化Netty客户端的ChannelPipeline。
             *
             * @param ch SocketChannel对象，用于添加解码器、编码器和处理器。
             */
            @Override
            protected void initChannel(SocketChannel ch) {
                ch.pipeline().addLast(new LineBasedFrameDecoder(1024)).addLast(new StringDecoder()).addLast(new StringEncoder()).addLast(new ServerHandler()).addLast(new ClientHandler(NettyClient.this));
            }
        });

        ChannelFuture future = bootstrap.connect(host, port).sync();
        channel = future.channel();
        isConnected = true;

        // 重连成功后发送缓存消息
        sendCachedMessages();
    }

    /**
     * 发送消息到服务器。
     *
     * @param message 要发送的消息内容。
     */
    public void sendMessage(String message) {
        if (isConnected && channel != null && channel.isActive()) {
            channel.writeAndFlush(message);
        } else {
            // 如果未连接，则缓存消息
            messageCache.offer(message);
            System.out.println("消息已缓存: " + message);
        }
    }

    /**
     * 发送所有缓存的消息到当前活动的通道。
     * 如果通道不存在或不活动，则不会发送任何消息。
     */
    private void sendCachedMessages() {
        String message;

        while ((message = messageCache.poll()) != null) {
            if (channel != null && channel.isActive()) {
                // 逐条发送，不添加换行符
                System.out.print("--缓存--发送消息: " + message);
                channel.writeAndFlush(message);
            }
        }
    }

    /**
     * 处理连接断开并尝试重新连接。
     */
    public void handleDisconnect() {
        isConnected = false;
        System.out.println("检测到连接断开，开始尝试重连...");
        reconnect();
    }

    /**
     * 尝试重新连接，直到成功或达到最大重试次数。
     */
    private void reconnect() {
        for (int i = 1; i <= MAX_RETRY_TIMES; i++) {
            try {
                System.out.println("第" + i + "次尝试重连...");
                doConnect();
                System.out.println("重连成功！");
                return;
            } catch (InterruptedException e) {
                System.err.println("重连被中断: " + e.getMessage());
                Thread.currentThread().interrupt();
                return;
            } catch (Exception e) {
                System.err.println("第" + i + "次重连失败: " + e.getMessage());
                if (i == MAX_RETRY_TIMES) {
                    System.err.println("达到最大重试次数，停止重连");
                    return;
                }

                try {
                    TimeUnit.MILLISECONDS.sleep(RETRY_INTERVAL);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
        }
    }

    /**
     * 处理用户输入的方法。
     * 在新线程中运行，读取用户输入并发送消息，直到用户输入“quit”或线程被中断。
     */
    private void handleUserInput() {
        new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.print("输入文字: ");
                    String input = scanner.nextLine();
                    if ("quit".equals(input)) {
                        if (channel != null) {
                            channel.close();
                        }
                        break;
                    }
                    sendMessage(input);
                }
            } catch (Exception e) {
                System.err.println("输入错误: " + e.getMessage());
            }
        }).start();
    }
}
